
import { App } from "../App";
import { Matrix, SocketParser, PkgData, EventConst } from "./Common";

const MAX_TRY_CONNECT:number = 30; // 断线重连最大次数
const HEARTBEAT_TIME:number = 50000; // 心跳间隔
const TRY_CONNECT_TIME:number = 5000; // 重连间隔
const MISSTAKE_TIME:number = 10; // 误差时间
const ONE_SECOND:number = 1000;// 一秒

// 数据包结构
interface PackData{
    stamp:number,
    svrCall:(data:any)=>void,
}

export class Socket extends Matrix {
    private wsHander:WebSocket = null;
    private isColsed:boolean = true;
    private isConnected:boolean = false;
    private strUrl:string = '';
    private strLastUrl:string = '';
    private sendList:any[] = [];
    private respCalls:Map<string,PackData> = new Map<string,PackData>();
    private tryConnetCount:number = 0;
    private tryHander:any = undefined;
    private heartbeat:any = undefined;
    private msgParser:SocketParser = null;

    /**
     *Creates an instance of _wsSocket.
     * @param {string} url
     * @param {boolean} [isJson=true]
     * @memberof _wsSocket
     */
    protected onInit(){
        App.Event.on(EventConst.SOCKET_CLOSE,this._closeSocket,this);
        App.Event.on(EventConst.SOCKET_CHANGE_CONNECTED,this._changeConnect,this);
        App.Event.on(EventConst.SOCKET_SEND,this._sendData,this);
        App.Event.on(EventConst.SOCKET_CONNETED,this._connected,this);
        App.Event.on(EventConst.SOCKET_BIND,(obj:SocketParser)=>{
            this.msgParser = obj;
        },this);

        let _hideTime = null;
        App.Event.on(EventConst.APP_HIDE,()=>{
            if (App.Util.isEmpty(this.strUrl) && App.Util.isEmpty(this.strLastUrl)) return ;

            _hideTime && clearTimeout(_hideTime);
            _hideTime = null;
            _hideTime = setTimeout(() => {
                clearTimeout(_hideTime);
                _hideTime = null;
                this._closeSocket();
            }, 2*HEARTBEAT_TIME);
        },this);

        App.Event.on(EventConst.APP_SHOW,()=>{
            if (App.Util.isEmpty(this.strUrl) && App.Util.isEmpty(this.strLastUrl)) return ;

            if (_hideTime){
                clearTimeout(_hideTime);
                _hideTime = null;
            }
            if (this.isColsed && !App.Util.isEmpty(this.strLastUrl)){
                console.log(this.strLastUrl);
                App.Event.emit(EventConst.LOCK_SCREEN,true,true);
                this._connected(this.strLastUrl,(is:boolean)=>{
                    if (is){
                        App.Event.emit(EventConst.SOCKET_AGAIN_CONNETED,true);
                        App.Event.emit(EventConst.LOCK_SCREEN,false,false);
                    }
                });
            }
        },this);
    }

    protected onDestroy(){
        this._closeSocket();
        App.Event.offAll(this);
    }

    private _closeSocket(){
        if (this.heartbeat){
            clearInterval(this.heartbeat);
            this.heartbeat = undefined;
        }

        this._stopTryConnect();
        this.sendList = [];
        this.respCalls.clear();
        this.wsHander && this.wsHander.close();
    };

    private _stopTryConnect(){
        if (this.tryHander){
            clearInterval(this.tryHander);
            this.tryHander = null;
        }
        this.tryConnetCount = 0;
    };

    private _changeConnect(url:string,cb?:(is:boolean)=>void){
        if (this.strUrl === url){
            return ;
        }

        this._closeSocket();
        this._connected(url,cb);
    };

    private _sendData(data:any,protocol?:string,bindCall?:(data:any)=>void){
        const pkg = this.msgParser.encode(data);
        if (!pkg) return ;

        if (!App.Util.isEmpty(protocol) && typeof bindCall === 'function'){
            this.respCalls.set(protocol,{stamp:(new Date()).getTime(),svrCall:bindCall});
        }
        this.isConnected ? this.wsHander.send(pkg) : this.sendList.push(pkg);
        !this.isConnected && this._connected(this.strUrl);
    };

    private _connected(url:string,cb?:(is:boolean)=>void){
        this.isConnected && this._closeSocket();

        try {
            this.wsHander = new WebSocket(url);
            !this.msgParser.isString && (this.wsHander.binaryType = 'arraybuffer');
            this.wsHander.onopen = (evt:Event)=>{
                if (this.wsHander.readyState !== 1) return ;
                this.strUrl = this.strLastUrl = url;
                this.isColsed = false;
                this.isConnected = true;
                this.tryConnetCount = 0;
                
                console.log(`${this.strUrl} connect success.`);

                this._stopTryConnect();
                this.heartbeat = setInterval(()=>{
                    this.msgParser.sendHeartbeat();
                },HEARTBEAT_TIME);

                const time = (new Date()).getTime();
                while (this.sendList.length > 0){
                    const data = this.sendList.shift();
                    if (data && Math.floor((time - data.stamp)/ONE_SECOND) < MISSTAKE_TIME){
                        this.wsHander.send(data);
                    }
                }

                cb && cb(true);
            };
    
            this.wsHander.onmessage = (evt:MessageEvent)=>{
                let pkg:PkgData = this.msgParser.decode(evt.data);
                if (App.Util.isValid(pkg)){
                    const pkgData = this.respCalls.get(pkg.proto);
                    pkgData && pkgData.svrCall(pkg.data);
                    this.msgParser.message(`${pkg.proto}`,pkg.data);
                }
            };
    
            this.wsHander.onclose = (evt:CloseEvent)=>{
                this.isColsed = true;
                this.isConnected = false;
            };
    
            this.wsHander.onerror = (evt:Event)=>{
                cb && cb(false);
                if (!this.isColsed){
                    this.isConnected = false;
                    if (++this.tryConnetCount < MAX_TRY_CONNECT){
                        App.Event.emit(EventConst.SOCKET_TRY_CONNECT,this.tryConnetCount);
                        if (!this.tryHander){
                            this.tryHander = setInterval(this._connected,TRY_CONNECT_TIME);
                        } else {
                            this._connected(this.strLastUrl);
                        }
                    } else {
                        App.Event.emit(EventConst.SOCKET_TRY_CONNECT_FAIL);
                        this._closeSocket();
                    }
                }
            };
        } catch (error) {
        }
    };
};

(function(){
    (new Socket()).init();
})();